3.4.4 函数式编程
介绍:
- 与面向对象编程(Object-oriented programming)和过程式编程(Procedural programming)并列的编程范式。
- 最主要的特征是,函数是第一等公民。
- 强调将计算过程分解成可复用的函数,典型例子就是map方法和reduce方法组合而成 MapReduce 算法。
- 只有纯的、没有副作用的函数,才是合格的函数。
函数式编程有两个最基本的运算:合成和柯里化。
函数的合成与柯里化
- 要经过多个函数,才能变成另外一个值,就可以把所有中间步骤合并成一个函数,这叫做"函数的合成"(compose)。
- 所谓"柯里化",就是把一个多参数的函数,转化为单参数函数。
函子
是函数式编程里面最重要的数据类型,也是基本的运算单位和功能单位
函子的代码实现
任何具有map方法的数据结构,都可以当作函子的实现。
class Functor {
constructor(val) {
this.val = val;
}
map(f) {
return new Functor(f(this.val));
}
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
函子的标志就是容器具有map方法。该方法将容器里面的每一个值,映射到另一个容器
学习函数式编程,实际上就是学习函子的各种运算
of
函数式编程一般约定,函子有一个of方法,用来生成新的容器。
下面就用of方法替换掉new。
Functor.of = function(val) {
return new Functor(val);
};
1
2
3
2
3
Maybe 函子
函子接受各种函数,处理容器内部的值。这里就有一个问题,容器内部的值可能是一个空值(比如null),
而外部函数未必有处理空值的机制,如果传入空值,很可能就会出错。
Functor.of(null).map(function (s) {
return s.toUpperCase();
});
// TypeError
1
2
3
4
2
3
4
上面代码中,函子里面的值是null,结果小写变成大写的时候就出错了。
Maybe 函子就是为了解决这一类问题而设计的。简单说,它的map方法里面设置了空值检查。
class Maybe extends Functor {
map(f) {
return this.val ? Maybe.of(f(this.val)) : Maybe.of(null);
}
}
1
2
3
4
5
2
3
4
5
Either 函子
条件运算if...else是最常见的运算之一,函数式编程里面,使用 Either 函子表达。
Either 函子内部有两个值:左值(Left)和右值(Right)。右值是正常情况下使用的值,左值是右值不存在时使用的默认值。
var addOne = function (x) {
return x + 1;
};
Either.of(5, 6).map(addOne);
// Either(5, 7);
Either.of(1, null).map(addOne);
// Either(2, null);
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
ap 函子
函子里面包含的值,完全可能是函数。我们可以想象这样一种情况,一个函子的值是数值,另一个函子的值是函数。
ap 是 applicative(应用)的缩写。凡是部署了ap方法的函子,就是 ap 函子。
class Ap extends Functor {
ap(F) {
return Ap.of(this.val(F.val));
}
}
1
2
3
4
5
2
3
4
5
注意,ap方法的参数不是函数,而是另一个函子。
Monad 函子
Maybe.of(
Maybe.of(
Maybe.of({name: 'Mulburry', number: 8402})
)
)
1
2
3
4
5
2
3
4
5
上面这个函子,一共有三个Maybe嵌套。如果要取出内部的值,就要连续取三次this.val。这当然很不方便,因此就出现了 Monad 函子。
Monad 函子的作用是,总是返回一个单层的函子。它有一个flatMap方法,与map方法作用相同,唯一的区别是如果生成了一个嵌套函子,
它会取出后者内部的值,保证返回的永远是一个单层的容器,不会出现嵌套的情况。